package com.starlinkdt.quartz.util;

import com.starlinkdt.common.constant.Constants;
import com.starlinkdt.common.constant.ScheduleConstants;
import com.starlinkdt.common.core.redis.RedisCache;
import com.starlinkdt.common.utils.ExceptionUtil;
import com.starlinkdt.common.utils.StringUtils;
import com.starlinkdt.common.utils.bean.BeanUtils;
import com.starlinkdt.common.utils.spring.SpringUtils;
import com.starlinkdt.quartz.domain.SysJob;
import com.starlinkdt.quartz.domain.SysJobLog;
import com.starlinkdt.quartz.service.ISysJobLogService;
import lombok.Setter;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.starlinkdt.common.constant.CacheConstants.JOB_RETRY_CNT_KEY;

/**
 * 抽象quartz调用
 *
 * @author system
 */
public abstract class AbstractQuartzJob implements Job {
    private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class);

    @Setter
    private static ApplicationContext applicationContext;

    /**
     * 最大重试次数
     */
    @Setter
    private int maxRetry = 3;

    /**
     * 执行间隔
     */
    @Setter
    private long interval = 6000;

    /**
     * 线程本地变量
     */
    private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // redis 重试次数
        RedisCache redisCache = applicationContext.getBean(RedisCache.class);
        // 获取任务
        SysJob sysJob = getSysJob(context);
        String jobRetryCntKey = JOB_RETRY_CNT_KEY + ":" + sysJob.getJobId();
        try {
            before(context, sysJob);
            if (!ObjectUtils.isEmpty(sysJob)) {
                log.info("开始执行任务：{}@{}", sysJob.getJobName(), sysJob.getJobGroup());
                // 业务方法
                doExecute(context, sysJob);
                log.info("执行任务完成：{}@{}", sysJob.getJobName(), sysJob.getJobGroup());
            }
            after(context, sysJob, null);
            // 成功执行后删除 retry 记录
            redisCache.deleteObject(jobRetryCntKey);
        } catch (Exception e) {
            Throwable actual = e;
            if (e instanceof InvocationTargetException) {
                actual = ((InvocationTargetException) e).getTargetException();
            }
            log.error("实际异常为：", actual);
            // 是否需要重试
            if (shouldRetry(actual)) {
                // 重试任务
                handleRetry(context, redisCache, sysJob, e);
            } else {
                // 执行失败
                handleException(context, sysJob, e);
            }
        }
    }

    /**
     * 执行前
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     */
    protected void before(JobExecutionContext context, SysJob sysJob) {
        threadLocal.set(new Date());
    }

    /**
     * 执行后
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     */
    protected void after(JobExecutionContext context, SysJob sysJob, Exception e) {
        Date startTime = threadLocal.get();
        if (startTime != null) {
            threadLocal.remove();
        }

        final SysJobLog sysJobLog = new SysJobLog();
        sysJobLog.setJobName(sysJob.getJobName());
        sysJobLog.setJobGroup(sysJob.getJobGroup());
        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
        sysJobLog.setStartTime(startTime);
        sysJobLog.setStopTime(new Date());
        long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
        sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时：" + runMs + "毫秒");
        if (e != null) {
            sysJobLog.setStatus(Constants.FAIL);
            String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
            sysJobLog.setExceptionInfo(errorMsg);
        } else {
            sysJobLog.setStatus(Constants.SUCCESS);
        }
        // 写入数据库当中
        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
    }

    /**
     * 获取系统计划任务
     *
     * @param context
     * @return
     */
    protected SysJob getSysJob(JobExecutionContext context) {
        JobDataMap mergedJobDataMap = context.getMergedJobDataMap();
        Object taskProperties = mergedJobDataMap.get(ScheduleConstants.TASK_PROPERTIES);

        if (taskProperties instanceof SysJob) {
            return (SysJob) taskProperties;
        }

        SysJob sysJob = new SysJob();
        BeanUtils.copyBeanProp(sysJob, taskProperties);
        return sysJob;
    }


    /**
     * 是否允许重试
     */
    protected boolean shouldRetry(Throwable ex) {
        // 默认只对 RuntimeException 重试
        return ex instanceof RuntimeException;
    }

    /**
     * 处理异常
     */
    protected void handleException(JobExecutionContext context, SysJob sysJob, Exception e) {
        log.error("任务执行异常 - {}", sysJob.getJobName(), e);
        after(context, sysJob, e);
    }

    /**
     * 处理重试任务
     */
    private void handleRetry(JobExecutionContext context, RedisCache redisCache, SysJob sysJob, Exception ex) {
        // 优先从 JobDataMap 获取原始任务 key
        String jobRetryCntKey = JOB_RETRY_CNT_KEY + ":" + sysJob.getJobId();
        String retryStr = (String) redisCache.getCacheObject(jobRetryCntKey);
        int retry = (retryStr != null) ? Integer.parseInt(retryStr) : 0;

        if (retry >= maxRetry) {
            log.error("{}次重试后任务执行失败", maxRetry);
            after(context, sysJob, ex);
            redisCache.deleteObject(jobRetryCntKey);
        } else {
            retry += 1;
            log.info("进行尝试，retry:{}", retry);

            redisCache.setCacheObject(jobRetryCntKey, String.valueOf(retry), 1, TimeUnit.HOURS);

            scheduleRetryJob(context, retry);
        }
    }

    /**
     * 调度重试任务
     */
    private void scheduleRetryJob(JobExecutionContext context, int retry) {
        try {
            JobDataMap jobDataMap = new JobDataMap();
            SysJob sysJob = getSysJob(context);
            jobDataMap.put(ScheduleConstants.TASK_PROPERTIES, sysJob);
            JobDetail jobDetail = JobBuilder.newJob(this.getClass())
                    .usingJobData(jobDataMap)
                    .build();

            long backoff = (long) (interval * Math.pow(2, retry));
            long maxDelay = 60_000;
            backoff = Math.min(backoff, maxDelay);

            Trigger trigger = TriggerBuilder.newTrigger()
                    .startAt(new Date(System.currentTimeMillis() + backoff))
                    .build();

            // 写入本次重试的日志
            writeRetryLog(sysJob, retry);
            context.getScheduler().scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 写入重试日志
     */
    private void writeRetryLog(SysJob sysJob, int retry) {
        final SysJobLog sysJobLog = new SysJobLog();
        sysJobLog.setJobName(sysJob.getJobName());
        sysJobLog.setJobGroup(sysJob.getJobGroup());
        sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
        sysJobLog.setStartTime(new Date());
        sysJobLog.setStopTime(new Date());
        sysJobLog.setJobMessage(sysJobLog.getJobName() + " - 第 " + retry + " 次重试");
        sysJobLog.setStatus(Constants.FAIL); // 重试代表之前失败了
        sysJobLog.setExceptionInfo("任务因异常进入重试流程，retry=" + retry);

        SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
    }



    /**
     * 执行方法，由子类重载
     *
     * @param context 工作执行上下文对象
     * @param sysJob  系统计划任务
     * @throws Exception 执行过程中的异常
     */
    protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception;
}
